// ==UserScript==
// @name         Xplus论坛媒体批量下载器优化版
// @version      3.0
// @author       coup
// @namespace    coupdegraces
// @license      MIT
// @description  下载论坛页面中的所有图片和视频（支持并发下载和重试机制）
// @match        *://*.east-plus.net/*
// @match        *://east-plus.net/*
// @match        *://*.south-plus.net/*
// @match        *://south-plus.net/*
// @match        *://*.south-plus.org/*
// @match        *://south-plus.org/*
// @match        *://*.white-plus.net/*
// @match        *://white-plus.net/*
// @match        *://*.north-plus.net/*
// @match        *://north-plus.net/*
// @match        *://*.level-plus.net/*
// @match        *://level-plus.net/*
// @match        *://*.soul-plus.net/*
// @match        *://soul-plus.net/*
// @match        *://*.snow-plus.net/*
// @match        *://snow-plus.net/*
// @match        *://*.spring-plus.net/*
// @match        *://spring-plus.net/*
// @match        *://*.summer-plus.net/*
// @match        *://summer-plus.net/*
// @match        *://*.blue-plus.net/*
// @match        *://blue-plus.net/*
// @match        *://*.imoutolove.me/*
// @match        *://imoutolove.me/*
// @run-at       document-start
// @grant        GM_download
// @grant        GM_notification
// @grant        GM_xmlhttpRequest
// @grant        GM_setClipboard
// ==/UserScript==

(function () {
    'use strict';

    // 配置参数
    const CONFIG = {
        // 并发下载数量
        PARALLEL_NUM: 5,
        // 最大重试次数
        MAX_RETRIES: 3,
        // 重试延迟（毫秒）
        RETRY_DELAY: 2000,
        // 下载间隔（毫秒）
        DOWNLOAD_INTERVAL: 300
    };

    // 支持的媒体类型配置
    const videoDomains = ['files.catbox.moe', 'litter.catbox.moe', 'p.inari.site', 'img.bian666.cf', 'example-video.com', 'video-hosting.net'];
    const videoExtensions = ['mp4', 'mov', 'ts', 'avi', 'webm', 'mkv'];
    const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'];

    // 创建下载按钮
    const downloadBtn = document.createElement('button');
    downloadBtn.id = 'download-all-media-btn'; // 统一ID
    downloadBtn.innerHTML = '<i class="fas fa-download"></i> 下载全部媒体';
    Object.assign(downloadBtn.style, {
        position: 'fixed',
        left: '20px',
        bottom: '20px',
        zIndex: 9999,
        padding: '10px 15px',
        backgroundColor: '#4CAF50',
        color: 'white',
        border: 'none',
        borderRadius: '4px',
        cursor: 'pointer',
        boxShadow: '0 2px 10px rgba(0,0,0,0.2)',
        fontSize: '14px',
        fontWeight: 'bold',
        display: 'flex',
        alignItems: 'center'
    });

    // 添加到页面
    document.body.appendChild(downloadBtn);

    // 按钮点击事件处理
    downloadBtn.addEventListener('click', downloadAllMedia);

    // 获取帖子标题和序号
    function getPostInfo() {
        // 查找包含帖子标题的链接
        const titleLinks = document.querySelectorAll('a[href*="read.php?tid"]');
        let title = '未知标题';
        let tid = '未知序号';

        for (const link of titleLinks) {
            const href = link.getAttribute('href');
            const text = link.textContent.trim();

            if (href && text && href.includes('read.php?tid')) {
                // 从href中提取tid
                const tidMatch = href.match(/tid-(\d+)\.html/);
                if (tidMatch && tidMatch[1]) {
                    tid = tidMatch[1];
                    title = text;
                    break;
                }
            }
        }

        // 清理标题中的非法字符
        title = title.replace(/[\\/:*?"<>|]/g, '_');

        return { title, tid };
    }

    // 获取帖子内容区域（优化搜索范围）
    function getPostContentArea() {
        // 优先查找常见的帖子内容区域
        const contentSelectors = [
            '.tpc_content',
            '#cate_thread #tab_1',
            '.post_content',
            '.message',
            'article'
        ];

        for (const selector of contentSelectors) {
            const element = document.querySelector(selector);
            if (element) {
                return element;
            }
        }

        // 如果没有找到特定区域，返回整个body
        return document.body;
    }

    // 获取所有视频链接（优化版，限制搜索范围）
    function getVideoLinks() {
        const videoLinks = [];
        const contentArea = getPostContentArea();

        // 1. 在内容区域内查找<a>标签中的视频链接
        contentArea.querySelectorAll('a').forEach(a => {
            const href = a.href;
            if (href && isVideoLink(href)) {
                videoLinks.push(href);
            }
        });

        // 2. 在内容区域文本中搜索视频链接
        const contentText = contentArea.innerText || contentArea.textContent;
        const videoRegex = new RegExp(
            `https?:\\/\\/(${videoDomains.join('|')})\\/[^\\s\\]\\[\\)\\(]+?\\.(${videoExtensions.join('|')})`,
            'gi'
        );

        let match;
        while ((match = videoRegex.exec(contentText)) !== null) {
            videoLinks.push(match[0]);
        }

        // 去重
        return [...new Set(videoLinks)];
    }

    // 获取文本中的图片直链
    function getImageLinksFromText() {
        const imageLinks = [];
        const contentArea = getPostContentArea();
        const contentText = contentArea.innerText || contentArea.textContent;

        // 匹配常见的图片直链格式
        const imageRegex = new RegExp(
            `https?:\\/\\/[^\\s\\]\\[\\)\\(]+\\.(${imageExtensions.join('|')})(?=\\s|$|\\]|\\[|\\)|\\()`,
            'gi'
        );

        let match;
        while ((match = imageRegex.exec(contentText)) !== null) {
            const url = match[0];
            // 排除常见的不需要下载的图片（如表情、图标等）
            if (!url.includes('/smile/') &&
                !url.includes('/face/') &&
                !url.includes('avatar') &&
                !url.includes('icon')) {
                imageLinks.push(url);
            }
        }

        return [...new Set(imageLinks)];
    }

    // 判断链接是否为视频链接
    function isVideoLink(url) {
        try {
            const parsedUrl = new URL(url);
            const domain = parsedUrl.hostname;
            const path = parsedUrl.pathname.toLowerCase();

            // 检查域名是否在支持列表中
            const domainMatch = videoDomains.some(d => domain.includes(d));

            // 检查文件扩展名
            const extMatch = videoExtensions.some(ext => path.endsWith(`.${ext}`));

            return domainMatch && extMatch;
        } catch (e) {
            return false;
        }
    }

    // 带重试的下载函数
    async function downloadWithRetry(url, filename, retryCount = 0) {
        return new Promise((resolve, reject) => {
            GM_download({
                url: url,
                name: filename,
                onload: () => {
                    console.log(`下载成功: ${filename}`);
                    resolve(true);
                },
                onerror: (error) => {
                    console.error(`下载失败 (尝试 ${retryCount + 1}/${CONFIG.MAX_RETRIES + 1}): ${filename}`, error);

                    if (retryCount < CONFIG.MAX_RETRIES) {
                        setTimeout(() => {
                            console.log(`重试下载: ${filename}`);
                            downloadWithRetry(url, filename, retryCount + 1)
                                .then(resolve)
                                .catch(reject);
                        }, CONFIG.RETRY_DELAY);
                    } else {
                        reject(new Error(`下载失败: ${filename}, 错误: ${error.error}`));
                    }
                }
            });
        });
    }

    // 并发下载控制器
    async function downloadWithConcurrency(downloadItems, updateProgress) {
        const results = { success: 0, failed: 0, errors: [] };
        let currentIndex = 0;

        // 创建并发任务
        const workers = Array(CONFIG.PARALLEL_NUM).fill().map(async (_, workerId) => {
            while (currentIndex < downloadItems.length) {
                const index = currentIndex++;
                if (index >= downloadItems.length) break;

                const item = downloadItems[index];
                try {
                    await new Promise(resolve => setTimeout(resolve, index * CONFIG.DOWNLOAD_INTERVAL));
                    await downloadWithRetry(item.url, item.filename);
                    results.success++;
                } catch (error) {
                    results.failed++;
                    results.errors.push({
                        url: item.url,
                        filename: item.filename,
                        error: error.message
                    });

                    // 发送失败通知
                    GM_notification({
                        text: `下载失败: ${item.filename}`,
                        title: '下载错误',
                        timeout: 5000,
                        image: 'https://img.icons8.com/color/48/000000/error--v1.png'
                    });
                }

                updateProgress(results.success + results.failed, downloadItems.length);
            }
        });

        await Promise.all(workers);
        return results;
    }

    // 媒体下载主函数
    async function downloadAllMedia() {
        const { title, tid } = getPostInfo();

        try {
            // 获取所有需要下载的图片（DOM中的图片）
            const domImages = [
                ...document.querySelectorAll('#cate_thread #tab_1 img'),
                ...document.querySelectorAll('.tpc_content img')
            ].filter(img => {
                // 排除安全模式替换的头像和表情图片
                return !img.src.includes('/images/face/none.gif') &&
                    !img.src.includes('images/post/smile/') &&
                    !img.src.includes('avatar') &&
                    img.src &&
                    !img.src.startsWith('data:');
            });

            // 获取文本中的图片直链
            const textImageLinks = getImageLinksFromText();

            // 获取所有视频链接
            const videos = getVideoLinks();

            // 准备下载项
            const downloadItems = [];

            // DOM图片
            domImages.forEach((img, index) => {
                const imgUrl = getOriginalImageUrl(img);
                const filename = getImageFilename(imgUrl, index + 1, title, tid);
                downloadItems.push({ url: imgUrl, filename, type: 'image' });
            });

            // 文本中的图片直链
            textImageLinks.forEach((url, index) => {
                const filename = getImageFilename(url, domImages.length + index + 1, title, tid);
                downloadItems.push({ url, filename, type: 'text_image' });
            });

            // 视频
            videos.forEach((url, index) => {
                const filename = getVideoFilename(url, index + 1, title, tid);
                downloadItems.push({ url, filename, type: 'video' });
            });

            const totalMedia = downloadItems.length;

            if (totalMedia === 0) {
                GM_notification({
                    text: '未找到可下载的媒体文件',
                    title: '媒体下载',
                    timeout: 3000
                });
                return;
            }

            // 添加下载中状态
            downloadBtn.innerHTML = `<i class="fas fa-spinner fa-spin"></i> 下载中 (0/${totalMedia})`;
            downloadBtn.disabled = true;

            // 进度更新函数
            const updateProgress = (current, total) => {
                downloadBtn.innerHTML = `<i class="fas fa-spinner fa-spin"></i> 下载中 (${current}/${total})`;
            };

            // 执行并发下载
            const results = await downloadWithConcurrency(downloadItems, updateProgress);

            // 下载完成处理
            downloadBtn.innerHTML = '<i class="fas fa-download"></i> 下载全部媒体';
            downloadBtn.disabled = false;

            // 显示最终结果通知
            let resultText = `下载完成！成功: ${results.success}, 失败: ${results.failed}`;
            if (results.failed > 0) {
                resultText += `\n失败详情已输出到控制台`;
                console.error('下载失败的项目:', results.errors);

                // 可选：将失败列表复制到剪贴板
                if (results.errors.length > 0) {
                    const errorText = results.errors.map(e => `${e.filename}: ${e.url}`).join('\n');
                    GM_setClipboard(errorText, 'text');
                }
            }

            GM_notification({
                text: resultText,
                title: '下载完成',
                timeout: 5000
            });

        } catch (error) {
            console.error('下载过程发生错误:', error);
            GM_notification({
                text: `下载过程出错: ${error.message}`,
                title: '下载错误',
                timeout: 5000
            });

            // 恢复按钮状态
            downloadBtn.innerHTML = '<i class="fas fa-download"></i> 下载全部媒体';
            downloadBtn.disabled = false;
        }
    }

    // 处理下载完成（旧版本兼容，新版本已整合到主函数）
    function handleDownloadComplete(count, total) {
        // 此函数在新版本中已不再主要使用，保留用于兼容
        const btn = document.getElementById('download-all-media-btn');
        if (btn) {
            btn.innerHTML = `<i class="fas fa-spinner fa-spin"></i> 下载中 (${count}/${total})`;

            if (count === total) {
                btn.innerHTML = '<i class="fas fa-download"></i> 下载全部媒体';
                btn.disabled = false;
            }
        }
    }

    // 获取原始图片URL（处理延迟加载）
    function getOriginalImageUrl(img) {
        // 检查常见的延迟加载属性
        const lazyAttributes = [
            'data-src',
            'data-original',
            'data-lazy-src',
            'data-rinsp-defer-src',
            'data-srcset'
        ];

        for (const attr of lazyAttributes) {
            if (img.hasAttribute(attr)) {
                const lazySrc = img.getAttribute(attr);
                // 处理srcset的情况
                if (attr === 'data-srcset') {
                    const firstUrl = lazySrc.split(',')[0].split(' ')[0];
                    return firstUrl;
                }
                return lazySrc;
            }
        }

        return img.src;
    }

    // 生成图片文件名
    function getImageFilename(url, index, title, tid) {
        const extMatch = url.match(new RegExp(`\\.(${imageExtensions.join('|')})`, 'i'));
        const extension = extMatch ? extMatch[1].toLowerCase() : 'jpg';
        return `${title}_${tid}_P${index}.${extension}`;
    }

    // 生成视频文件名
    function getVideoFilename(url, index, title, tid) {
        const extMatch = url.match(new RegExp(`\\.(${videoExtensions.join('|')})`, 'i'));
        const extension = extMatch ? extMatch[1].toLowerCase() : 'mp4';
        return `${title}_${tid}_V${index}.${extension}`;
    }
})();